#ifndef REST_RPC_META_UTIL_HPP
#define REST_RPC_META_UTIL_HPP

#include <functional>
#include "cplusplus_14.h"

/**
 * @brief This is a set of utility templates and functions for working with tuples and function types in C++.
 * The for_each and for_each_i functions are used to iterate over the elements of a tuple, while the function_traits template is used to extract information about a function type,
 * such as its return type, argument types, and arity (number of arguments).
 * The for_each function takes a tuple and a function as arguments, and applies the function to each element of the tuple.
 * The for_each_i function is similar, but it also passes the index of the current element to the function.
 * The function_traits template is specialized for different types of functions, such as free functions, member functions, and functors,
 * and provides a way to extract information about the function's return type, argument types, and arity.
 * The remove_const_reference_t is a type alias that remove both const and reference from the provided type.
 * make_tuple_from_sequence is a function that creates a tuple from a sequence of integers, for example make_tuple_from_sequence<3>() will return a tuple (0, 1, 2)
 * detail::tuple_switch is a helper function for switching over a tuple's elements. It takes a tuple, a function and an index as input, and calls the function with the element of the tuple at the given index.
 * All of these utility functions and templates are used to make working with tuples and function types more convenient and less error-prone.
 *
 */

namespace rest_rpc
{

  template <typename... Args, typename Func, std::size_t... Idx>
  void for_each(const std::tuple<Args...> &t, Func &&f,
                std::index_sequence<Idx...>)
  {
    (void)std::initializer_list<int>{(f(std::get<Idx>(t)), void(), 0)...};
  }

  template <typename... Args, typename Func, std::size_t... Idx>
  void for_each_i(const std::tuple<Args...> &t, Func &&f,
                  std::index_sequence<Idx...>)
  {
    (void)std::initializer_list<int>{
        (f(std::get<Idx>(t), std::integral_constant<size_t, Idx>{}), void(),
         0)...};
  }

  template <typename T>
  struct function_traits;

  template <typename Ret, typename Arg, typename... Args>
  struct function_traits<Ret(Arg, Args...)>
  {
  public:
    enum
    {
      arity = sizeof...(Args) + 1
    };
    typedef Ret function_type(Arg, Args...);
    typedef Ret return_type;
    using stl_function_type = std::function<function_type>;
    typedef Ret (*pointer)(Arg, Args...);

    typedef std::tuple<Arg, Args...> tuple_type;
    typedef std::tuple<std::remove_const_t<std::remove_reference_t<Arg>>,
                       std::remove_const_t<std::remove_reference_t<Args>>...>
        bare_tuple_type;
    using args_tuple =
        std::tuple<std::string, Arg,
                   std::remove_const_t<std::remove_reference_t<Args>>...>;
    using args_tuple_2nd =
        std::tuple<std::string,
                   std::remove_const_t<std::remove_reference_t<Args>>...>;
  };

  template <typename Ret>
  struct function_traits<Ret()>
  {
  public:
    enum
    {
      arity = 0
    };
    typedef Ret function_type();
    typedef Ret return_type;
    using stl_function_type = std::function<function_type>;
    typedef Ret (*pointer)();

    typedef std::tuple<> tuple_type;
    typedef std::tuple<> bare_tuple_type;
    using args_tuple = std::tuple<std::string>;
    using args_tuple_2nd = std::tuple<std::string>;
  };

  template <typename Ret, typename... Args>
  struct function_traits<Ret (*)(Args...)> : function_traits<Ret(Args...)>
  {
  };

  template <typename Ret, typename... Args>
  struct function_traits<std::function<Ret(Args...)>>
      : function_traits<Ret(Args...)>
  {
  };

  template <typename ReturnType, typename ClassType, typename... Args>
  struct function_traits<ReturnType (ClassType::*)(Args...)>
      : function_traits<ReturnType(Args...)>
  {
  };

  template <typename ReturnType, typename ClassType, typename... Args>
  struct function_traits<ReturnType (ClassType::*)(Args...) const>
      : function_traits<ReturnType(Args...)>
  {
  };

  template <typename Callable>
  struct function_traits : function_traits<decltype(&Callable::operator())>
  {
  };

  template <typename T>
  using remove_const_reference_t =
      std::remove_const_t<std::remove_reference_t<T>>;

  template <size_t... Is>
  auto make_tuple_from_sequence(std::index_sequence<Is...>)
      -> decltype(std::make_tuple(Is...))
  {
    std::make_tuple(Is...);
  }

  template <size_t N>
  constexpr auto make_tuple_from_sequence()
      -> decltype(make_tuple_from_sequence(std::make_index_sequence<N>{}))
  {
    return make_tuple_from_sequence(std::make_index_sequence<N>{});
  }

  namespace detail
  {
    template <class Tuple, class F, std::size_t... Is>
    void tuple_switch(const std::size_t i, Tuple &&t, F &&f,
                      std::index_sequence<Is...>)
    {
      (void)std::initializer_list<int>{
          (i == Is &&
           ((void)std::forward<F>(f)(std::integral_constant<size_t, Is>{}), 0))...};
    }
  } // namespace detail

  template <class Tuple, class F>
  inline void tuple_switch(const std::size_t i, Tuple &&t, F &&f)
  {
    constexpr auto N = std::tuple_size<std::remove_reference_t<Tuple>>::value;

    detail::tuple_switch(i, std::forward<Tuple>(t), std::forward<F>(f),
                         std::make_index_sequence<N>{});
  }

  template <int N, typename... Args>
  using nth_type_of = std::tuple_element_t<N, std::tuple<Args...>>;

  template <typename... Args>
  using last_type_of = nth_type_of<sizeof...(Args) - 1, Args...>;
} // namespace rest_rpc

#endif // REST_RPC_META_UTIL_HPP
